package com.cskaoyan.wordmemorize.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cskaoyan.wordmemorize.auth.StpKit;
import com.cskaoyan.wordmemorize.common.constant.ResultCodeEnum;
import com.cskaoyan.wordmemorize.common.exception.BusinessException;
import com.cskaoyan.wordmemorize.converter.ReviewConverter;
import com.cskaoyan.wordmemorize.converter.UserConverter;
import com.cskaoyan.wordmemorize.dao.entity.*;
import com.cskaoyan.wordmemorize.dao.mapper.*;
import com.cskaoyan.wordmemorize.dto.admin.PageDTO;
import com.cskaoyan.wordmemorize.dto.app.NextLevelDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinStatisticsDTO;
import com.cskaoyan.wordmemorize.request.PageRequest;
import com.cskaoyan.wordmemorize.request.UserCheckinCommand;
import com.cskaoyan.wordmemorize.service.UserCheckinService;
import com.cskaoyan.wordmemorize.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;

@Service
public class UserCheckinServiceImpl implements UserCheckinService {
    @Autowired
    UserCheckinStatisticsMapper userCheckinStatisticsMapper;
    @Autowired
    UserCheckinMapper userCheckinMapper;
    @Autowired
    ReviewConverter reviewConverter;
    @Autowired
    UserVocBoundStatisticsMapper userVocBoundStatisticsMapper;
    @Autowired
    UserVocBoundLogMapper userVocBoundLogMapper;
    @Autowired
    UserService userService;
    @Autowired
    UserConverter userConverter;
    @Autowired
    UserMapper userMapper;
    @Autowired
    LevelMapper levelMapper;


    // APP端下载打卡数据(如果分页参数没有传的话，就返回所有打卡数据)
    @Override
    public PageDTO<UserCheckinDTO> getCheckInData(PageRequest pageRequest, Long userId) {

        // 根据用户Id查询用户打卡数据
        LambdaQueryWrapper<UserCheckinDO> queryWrapper = new LambdaQueryWrapper<UserCheckinDO>()
                .eq(UserCheckinDO::getUserId, userId);

        // 检查分页参数是否为null，如果是null，就返回所有打卡数据
        if (pageRequest.getPageNum() == null) {
            List<UserCheckinDO> userCheckinDOs = userCheckinMapper.selectList(queryWrapper);
            // 如果用户打卡数据不为空，就返回用户打卡数据，否则返回null
            if (userCheckinDOs != null) {
                PageDTO<UserCheckinDTO> userCheckinDTOPageDTO = reviewConverter.userCheckinPage2PageDTO(userCheckinDOs, (long) userCheckinDOs.size());
                return userCheckinDTOPageDTO;
            }
            return null;
        }

        // 如果分页参数不为null，就返回分页数据
        // 计算分页参数
        int pageSize = pageRequest.getCount();
        int pageNum = pageRequest.getPageNum();

        Page<UserCheckinDO> pageParam = new Page<>(pageNum, pageSize);
        Page<UserCheckinDO> userCheckinDOs = userCheckinMapper.selectPage(pageParam, queryWrapper);

        // 如果用户打卡数据不为空，就返回用户打卡数据，否则返回null
        if (userCheckinDOs != null) {
            PageDTO<UserCheckinDTO> userCheckinDTOPageDTO = reviewConverter.userCheckinPage2PageDTO(userCheckinDOs.getRecords(), userCheckinDOs.getTotal());
            return userCheckinDTOPageDTO;
        }
        return null;
    }


    // APP端打卡
    @Transactional
    @Override
    public UserCheckinStatisticsDTO checkin(UserCheckinCommand command) {
        Long userId = StpKit.USER.getLoginIdAsLong();
        // Long userId = Long.valueOf(command.getUserId());// 测试用
        // todo:判断用户签到是否是今天
        if (!LocalDate.now().equals(command.getSignDate())) {
            // todo:若用户签到日期不是今天，抛出异常打卡日期不是今天
            throw new BusinessException(ResultCodeEnum.CHECK_IN_DATE_NOT_TODAY);
        }
        // todo:判断用户是否已经签到过
        LambdaQueryWrapper<UserCheckinDO> userCheckinQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinQueryWrapper.eq(UserCheckinDO::getSignDate, LocalDate.now())
                .eq(UserCheckinDO::getUserId, userId);
        UserCheckinDO userCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
        if (userCheckinDO != null) {
            // todo:用户已经签到过，抛出异常打卡日期重复
            throw new BusinessException(ResultCodeEnum.CHECK_IN_DATE_DUPLICATE);
        }
        // todo:每签到成功一次，用户的总单词上限和可用单词上限增加1(user_voc_bound_statistics表)
        LambdaUpdateWrapper<UserVocBoundStatisticsDO> userVocBoundStatisticsUpdateWrapper = new LambdaUpdateWrapper<>();
        userVocBoundStatisticsUpdateWrapper.eq(UserVocBoundStatisticsDO::getUserId, userId)
                .setSql("total = total + 1 ")
                .setSql("available = available + 1");
        userVocBoundStatisticsMapper.update(userVocBoundStatisticsUpdateWrapper);

        // todo:给用户单词上限的变化增加一条单词上限变化日志(user_voc_bound_log表)
        UserVocBoundLogDO userVocBoundLogDO = UserVocBoundLogDO.builder()
                .userId(userId)
                .logDate(command.getSignDate())
                .type(2)
                .count(1)
                .description("签到奖励")
                .build();
        userVocBoundLogMapper.insert(userVocBoundLogDO);

        UserCheckinDO todayUserCheckinDO = new UserCheckinDO();
        userCheckinQueryWrapper.clear();
        userCheckinQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, LocalDate.now().minusDays(1));

        // todo:判断昨天是否签到，若昨天签到过，今天签到段号设置为与昨天连续签到段号相同
        UserCheckinDO yesterdayUserCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
        LambdaQueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinStatisticsQueryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsQueryWrapper);
        if (yesterdayUserCheckinDO != null) {
            todayUserCheckinDO.setStickNo(yesterdayUserCheckinDO.getStickNo());
            todayUserCheckinDO.setUserId(userId);
            todayUserCheckinDO.setSignDate(LocalDate.now());
            todayUserCheckinDO.setType(1);
            userCheckinMapper.insert(todayUserCheckinDO);
            // todo:更新user_checkin_statistics
            userCheckinStatisticsDO.setTotalDays(userCheckinStatisticsDO.getTotalDays() + 1);
            Integer curContinuousDays = userCheckinStatisticsDO.getCurContinuousDays() + 1;
            Integer maxContinuousDays = userCheckinStatisticsDO.getMaxContinuousDays();
            LocalDate maxContinuousStartDate = userCheckinStatisticsDO.getMaxContinuousStartDate();
            LocalDate curContinuousStartDate = userCheckinStatisticsDO.getCurContinuousStartDate();
            // todo:如果当前最大连续签到天数大于最大连续签到天数，更新最大连续签到天数和最大连续签到天数的起始日期
            if (curContinuousDays > maxContinuousDays) {
                maxContinuousDays = curContinuousDays;
                maxContinuousStartDate = curContinuousStartDate;
            }
            userCheckinStatisticsDO.setCurContinuousDays(curContinuousDays);
            userCheckinStatisticsDO.setMaxContinuousDays(maxContinuousDays);
            userCheckinStatisticsDO.setMaxContinuousStartDate(maxContinuousStartDate);
            userCheckinStatisticsMapper.updateById(userCheckinStatisticsDO);
        } else {
            // todo:若昨天没签到，开启新的连续签到，签到段号就是签到的id值
            todayUserCheckinDO.setUserId(userId);
            todayUserCheckinDO.setSignDate(LocalDate.now());
            todayUserCheckinDO.setType(1);
            userCheckinMapper.insert(todayUserCheckinDO);
            // todo:主键回填
            todayUserCheckinDO.setStickNo(todayUserCheckinDO.getId());
            userCheckinMapper.updateById(todayUserCheckinDO);
            // todo:更新user_checkin_statistics
            userCheckinStatisticsDO.setTotalDays(userCheckinStatisticsDO.getTotalDays() + 1);
            userCheckinStatisticsDO.setCurContinuousDays(1);
            userCheckinStatisticsDO.setCurContinuousStartDate(LocalDate.now());
            if (userCheckinStatisticsDO.getMaxContinuousStartDate() == null) {
                userCheckinStatisticsDO.setMaxContinuousStartDate(LocalDate.now());
            }
            if (0 == userCheckinStatisticsDO.getMaxContinuousDays()) {
                userCheckinStatisticsDO.setMaxContinuousDays(1);
            }
            userCheckinStatisticsMapper.updateById(userCheckinStatisticsDO);
        }

        // todo:更新用户等级
        NextLevelDTO userNextDTO = userService.getUserNextDTO(userId);
        Integer remainDays = userNextDTO.getRemainDays();
        // todo:若用户升级所需天数为-1,说明已满级,不更新等级,直接返回
        if (-1 == remainDays) {
            return userConverter.userCheckinStatisticsDO2DTO(userCheckinStatisticsDO);
        }

        // todo:若remainDays==0，升级
        if (0 == remainDays) {
            UserDO userDO = userMapper.selectById(userId);
            Integer nextLevelValue = userDO.getLevelValue() + 1;
            LambdaQueryWrapper<LevelDO> levelDOQueryWrapper = new LambdaQueryWrapper<>();
            String levelName;
            LevelDO levelDO;
            // todo:如果用户即将升为6级，考虑当前连续签到天数，用户可能跳级
            if (6 == nextLevelValue) {
                // todo:获取连续签到天数
                Integer curContinuousDays = userCheckinStatisticsDO.getCurContinuousDays();
                // todo:与连续签到升级所需天数比较，判断用户是否跳级
                Integer plusOneFinalLevelValue = nextLevelValue + 1;
                levelDOQueryWrapper.eq(LevelDO::getLevelValue, plusOneFinalLevelValue);
                levelDO = levelMapper.selectOne(levelDOQueryWrapper);
                Integer checkInDays = levelDO.getCheckInDays();
                // todo:用户可能跳多级，判断用户最终等级
                while (curContinuousDays >= checkInDays) {
                    plusOneFinalLevelValue++;
                    levelDOQueryWrapper.clear();
                    levelDOQueryWrapper.eq(LevelDO::getLevelValue, plusOneFinalLevelValue);
                    levelDO = levelMapper.selectOne(levelDOQueryWrapper);
                    checkInDays = levelDO.getCheckInDays();
                }
                // todo:finalLevelValue为用户最终到达等级
                Integer finalLevelValue = plusOneFinalLevelValue - 1;
                levelDOQueryWrapper.clear();
                levelDOQueryWrapper.eq(LevelDO::getLevelValue, finalLevelValue);
                levelDO = levelMapper.selectOne(levelDOQueryWrapper);
                userDO.setLevelValue(finalLevelValue);
                userDO.setLevelDate(LocalDate.now());
                userDO.setLevelName(levelDO.getLevelName());
            } else {
                userDO.setLevelValue(nextLevelValue);
                levelDOQueryWrapper.clear();
                levelDOQueryWrapper.eq(LevelDO::getLevelValue, nextLevelValue);
                levelDO = levelMapper.selectOne(levelDOQueryWrapper);
                levelName = levelDO.getLevelName();
                userDO.setLevelName(levelName);
                userDO.setLevelDate(LocalDate.now());
            }
            userMapper.updateById(userDO);
        }

        return userConverter.userCheckinStatisticsDO2DTO(userCheckinStatisticsDO);
    }


    // APP端获取用户打卡数据
    @Override
    public UserCheckinStatisticsDTO getUserCheckinStatistics(Long userId) {

        // 根据用户Id查询用户打卡数据
        LambdaQueryWrapper<UserCheckinStatisticsDO> queryWrapper = new LambdaQueryWrapper<UserCheckinStatisticsDO>()
                .eq(UserCheckinStatisticsDO::getUserId, userId)
                .last("limit 1");

        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(queryWrapper);

        // 如果用户打卡数据不为空，就返回用户打卡数据，否则返回null
        if (userCheckinStatisticsDO != null) {
            // 把用户的学习时间total_time从ms转换为分钟
            Long totalTime = userCheckinStatisticsDO.getTotalTime();
            if (totalTime!= null) {
                long minutes = totalTime / 60000;
                userCheckinStatisticsDO.setTotalTime(minutes);
            }
            UserCheckinStatisticsDTO userCheckinStatisticsDTO = reviewConverter.userCheckinStatisticsDO2DTO(userCheckinStatisticsDO);
            return userCheckinStatisticsDTO;
        }

        return null;
    }

    /*
     APP端获取用户签到统计数据，注意:
     1. 连续签到是指从今天或者昨天(今天可能尚未打卡)向前计算的连续打卡天数
     2. 如果用户最近一次的连续签到不是今天或者昨天，那么最近连续签到天数为0
    */
    @Override
    public UserCheckinStatisticsDTO getUserContinuousCheckinInfo(Long userId) {
        return null;
    }

    /*
    APP端使用补签卡
    */
    @Transactional
    @Override
    public void userResignCard(Long userId) {
        // todo:判断用户是否完成当天签到，若未完成，不允许补签
        LambdaQueryWrapper<UserCheckinDO> userCheckinQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, LocalDate.now());
        UserCheckinDO userCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
        if (userCheckinDO == null) {
            // todo:当天用户没签到，不允许补签
            throw new BusinessException(ResultCodeEnum.USER_CHECKIN_DATA_NEED_SYNC);
        }
        // todo:获取当前签到段段号
        Long nowStickNo = userCheckinDO.getStickNo();

        // todo:判断用户是否有补签卡
        UserDO userDO = userMapper.selectById(userId);
        if (userDO.getHasSignCard() != 1) {
            // todo:用户无补签卡，抛出异常
            throw new BusinessException(ResultCodeEnum.USER_DONT_HAVE_RESIGN_CARD);
        }
        // todo:当用户的历史连续签到天数大于当前连续签到天数时，就可以使用补签卡，否则不能使用
        LambdaQueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinStatisticsQueryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
        UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsQueryWrapper);
        Integer maxContinuousDays = userCheckinStatisticsDO.getMaxContinuousDays();
        Integer curContinuousDays = userCheckinStatisticsDO.getCurContinuousDays();
        if (maxContinuousDays <= curContinuousDays) {
            throw new BusinessException(ResultCodeEnum.CANT_USE_CARD);
        }
        // todo:将用户补签卡的数量置为0
        userDO.setHasSignCard(0);
        userMapper.updateById(userDO);
        // todo:获取总签到天数
        Integer totalDays = userCheckinStatisticsDO.getTotalDays();
        // todo:根据历史最大签到天数，计算理想补签日期
        Integer gapDay = maxContinuousDays - curContinuousDays;
        // todo:获取理想补签日期和用户当前最大连续签到起始日期
        LocalDate curContinuousStartDate = userCheckinStatisticsDO.getCurContinuousStartDate();
        LocalDate idealDate = curContinuousStartDate.minusDays(gapDay);
        LocalDate realDate = idealDate;
        // todo:根据理想补签日期判断实际补签日期（理想补签日期可能已有签到）
        userCheckinQueryWrapper.clear();
        userCheckinQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, realDate);
        UserCheckinDO beginUserCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
        while (beginUserCheckinDO != null) {
            realDate = realDate.plusDays(1);
            userCheckinQueryWrapper.clear();
            userCheckinQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                    .eq(UserCheckinDO::getSignDate, realDate);
            beginUserCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
        }
        // todo:修改当前连续签到的起始日期
        userCheckinStatisticsDO.setCurContinuousStartDate(realDate);

        // todo:真实补签日期到今天的天数，即为新的连续签到天数。(ChronoUnit.DAYS.between计算天数差，结果需要加1)
        Integer newCurContinuousDays = (int) ChronoUnit.DAYS.between(realDate, LocalDate.now()) + 1;
        userCheckinStatisticsDO.setCurContinuousDays(newCurContinuousDays);
        // todo:从当前真实补签日期开始，到当前连续签到的起始日期之间，
        // todo:如果没有签到记录，就插入(段号为当前签到段段号)，如果有就修改其连续签到段号为当前连续签到段段号
        UserCheckinDO insertUserCheckinDO = new UserCheckinDO();
        insertUserCheckinDO.setUserId(userId);
        insertUserCheckinDO.setStickNo(nowStickNo);
        insertUserCheckinDO.setType(2);
        // todo:从真实补签日期开始补签至用户当前最大连续签到起始日期前一天
        // todo:统计从真实补签日期开始补签至用户当前最大连续签到起始日期前一天中无签到日期的数量，用于更新总的累计签到天数
        int supply = 0;
        while (realDate.isBefore(curContinuousStartDate)) {
            userCheckinQueryWrapper.clear();
            userCheckinQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                    .eq(UserCheckinDO::getSignDate, realDate);
            UserCheckinDO ifExistedUserCheckinDO = userCheckinMapper.selectOne(userCheckinQueryWrapper);
            insertUserCheckinDO.setSignDate(realDate);
            if (ifExistedUserCheckinDO == null) {
                // todo:当前日期无签到数据，直接插入
                insertUserCheckinDO.setId(null);
                userCheckinMapper.insert(insertUserCheckinDO);
                supply++;
            } else {
                // todo:当前日期有签到数据，更新
                ifExistedUserCheckinDO.setType(2);
                ifExistedUserCheckinDO.setStickNo(nowStickNo);
                userCheckinMapper.updateById(ifExistedUserCheckinDO);
            }
            realDate = realDate.plusDays(1);
        }
        // todo:用户总签到天数
        totalDays = totalDays + supply;
        userCheckinStatisticsDO.setTotalDays(totalDays);
        userCheckinStatisticsMapper.updateById(userCheckinStatisticsDO);

        // todo:判断用户当前等级，若等级低于6级，先判断累计签到天数，若升至6级，再判断连续签到天数
        Integer levelValue = userDO.getLevelValue();
        Integer nextLevelValue = levelValue + 1;

        if (levelValue >= 12) {
            // todo:用户已满级，不用更新等级
            return;
        }
        LambdaQueryWrapper<LevelDO> levelDOQueryWrapper = new LambdaQueryWrapper<>();
        levelDOQueryWrapper.eq(LevelDO::getLevelValue, nextLevelValue);
        LevelDO levelDO = levelMapper.selectOne(levelDOQueryWrapper);
        Integer checkInDays = levelDO.getCheckInDays();
        while (totalDays >= checkInDays && nextLevelValue < 7) {
            levelValue++;
            nextLevelValue++;
            levelDOQueryWrapper.clear();
            levelDOQueryWrapper.eq(LevelDO::getLevelValue, nextLevelValue);
            levelDO = levelMapper.selectOne(levelDOQueryWrapper);
            checkInDays = levelDO.getCheckInDays();
        }
        // todo:若用户等级依然不到6级，即该等级为用户最终等级
        if (levelValue < 6) {
            levelDOQueryWrapper.clear();
            levelDOQueryWrapper.eq(LevelDO::getLevelValue, levelValue);
            levelDO = levelMapper.selectOne(levelDOQueryWrapper);
            userDO.setLevelName(levelDO.getLevelName());
            userDO.setLevelValue(levelValue);
            userDO.setLevelDate(LocalDate.now());
            userMapper.updateById(userDO);
        } else {
            // todo:若此时用户等级为6及以上，则根据新连续签到天数判断用户等级变化
            levelDOQueryWrapper.clear();
            levelDOQueryWrapper.eq(LevelDO::getLevelValue, nextLevelValue);
            levelDO = levelMapper.selectOne(levelDOQueryWrapper);
            checkInDays = levelDO.getCheckInDays();
            while (newCurContinuousDays >= checkInDays) {
                levelValue++;
                nextLevelValue++;
                levelDOQueryWrapper.clear();
                levelDOQueryWrapper.eq(LevelDO::getLevelValue, nextLevelValue);
                levelDO = levelMapper.selectOne(levelDOQueryWrapper);
                checkInDays = levelDO.getCheckInDays();
            }
            levelDOQueryWrapper.clear();
            levelDOQueryWrapper.eq(LevelDO::getLevelValue, levelValue);
            levelDO = levelMapper.selectOne(levelDOQueryWrapper);
            userDO.setLevelName(levelDO.getLevelName());
            userDO.setLevelValue(levelValue);
            userMapper.updateById(userDO);
        }
    }
}
